#include <cfloat>
#include "sound.h"
#include "transform.h"
#include <assert.h>
#include "datatypedefine.h"

namespace OpenAudio
{
	//Sound::FrameData::FrameData(): _gain(0.0f), _pitch(0.0f) , mPlaying(0)
	//{}
	//
	//Sound::FrameData::FrameData(float gain, float pitch, unsigned int playing): _gain(gain)
	//   , _pitch(pitch), mPlaying(playing)
	//{}
	//
	//Sound::FrameData::~FrameData()
	//{}

	ALint IsSource(ALuint source)
	{
		ALint result = alIsSource(source);
		alGetError();
		return result;
	}

	Sound::Sound(CoreBase::ActorBase* owneractor,const QString& name): CoreBase::Transformable(owneractor,name)
		, _filename(""), _playCB(NULL), _playCBData(NULL) , _stopCB(NULL) , _stopCBData(NULL)
		, _commandState(BIT(STOP)) , _source(AL_NONE) , _buffer(AL_NONE) , _isInitialized(false)
		, _gain(1.0f) , _pitch(1.0f), _secondOffset(0.0f), _maxDistance(3000) , _referenceDistance(1.0f) //GK added
		, _rolloffFactor(1.0f), _minGain(0.0f), _maxGain(1.0f) , _listenerRelative(false), _position()
		, _direction(), _velocity(), _userDefinedSource(false)
	{
		_circleShape = new CoreBase::CircleShape(osg::Vec3(0,0,0),0.1f,32);
		setPosition(osg::Vec3(0.0f, 0.0f, 0.0f));
		setDirection(osg::Vec3(0.0f, 0.0f, 0.0f));
		setVelocity(osg::Vec3(0.0f, 0.0f, 0.0f));
		setGain(1.0f);
		setPitch(1.0f);
		setRolloffFactor(1.0f);
		setMaxDistance(3000);
		setMinGain(0.0f);
		setMaxGain(1.0f);
		setReferenceDistance(1.0f); //GK added
		_mode = 0;
	}

	Sound::~Sound()
	{
		if (IsSource(_source))
		{
			alDeleteSources(1, &_source);
		} 
		removeSender(_sender);
	}

	void Sound::addedToScene(CoreBase::Scene* scene)
	{
		addSender(scene);   
		_sender = scene;
	}

	void Sound::onMessage(MessageData* data)
	{
		assert(data);
		ALint srcState;
		if (data->message == SYSTEM_MESSAGE_POST_FRAME)
		{
			if (IsSource(_source))
			{
				alGetSourcei(_source, AL_SOURCE_STATE, &srcState);
				if (srcState == AL_STOPPED && !isStopped())
				{
					stop();
				}
			}
		}
	}

	void Sound::setState(unsigned int flag)
	{
		_commandState |= BIT(flag);
		if (flag == PLAY)
		{
			resetState(STOP);
			//resetState(PAUSE);
			resetState(REWIND);
		}
		else if (flag == STOP)
		{
			resetState(PLAY);
			resetState(PAUSE);
			resetState(REWIND);
		}
		else if (flag == PAUSE)
		{
			//resetState(PLAY);
			resetState(STOP);
			resetState(REWIND);
		}
		else if (flag == REWIND)
		{
			resetState(PLAY);
			resetState(PAUSE);
			setState(STOP);
		}
		else if (flag == LOOP)
		{
			resetState(UNLOOP);
		}
		else if (flag == UNLOOP)
		{
			resetState(LOOP);
		}
		else if (flag == REL)
		{
			resetState(ABS);
		}
		else if (flag == ABS)
		{
			resetState(REL);
		}
	}

	bool Sound::restoreSource()
	{
		int isSource = IsSource(_source);
		if (isSource == AL_TRUE)
		{
			return true; // nothing to do -- source doesn't need to be restored
		}
		alGenSources(1, &_source);
		setBuffer(_buffer);
		setGain(_gain);
		setPitch(_pitch);
		setPlayTimeOffset(_secondOffset);
		setMaxDistance(_maxDistance);
		setReferenceDistance(_referenceDistance); //GK added
		setRolloffFactor(_rolloffFactor);
		setMinGain(_minGain);
		setMaxGain(_maxGain);
		setListenerRelative(_listenerRelative);
		setPosition(_position);
		setDirection(_direction);
		setVelocity(_velocity); 
		if (getState(LOOP))
		{
			setLooping(true);
		}
		else
		{
			setLooping(false);
		}
		return true;
	}

	void Sound::setFilename(const std::string& file)
	{
		_filename = file;
	}

	void Sound::clear()
	{
		_filename = "";
		_userDefinedSource = false;
		//clear out command queue
		while (_command.size())
		{
			_command.pop();
		}
		_commandState = BIT(STOP);
		_playCB     = NULL;
		_playCBData = NULL;
		_stopCB     = NULL;
		_stopCBData = NULL;
		if (getParent())
		{
			getParent()->removeChild(this);
		}
		releaseSource();
	}

	bool Sound::releaseSource()
	{
		bool retVal = true;   
		int sourceAllocated = IsSource(_source);
		if (sourceAllocated == AL_TRUE)
		{
			alSourceStop(_source);      
			alDeleteSources(1, &_source);
			setSource(AL_NONE);
		}
		return retVal;
	}

	void Sound::runAllCommandsInQueue()
	{
		while (_command.size() > 0)
		{
			Command nextCmd = _command.front();
			_command.pop();
			if (nextCmd == PLAY)
			{
				if (_playCB)
				{
					_playCB(static_cast<Sound*>(this), _playCBData);
				}
				playImmediately();
			}
			else if (nextCmd == PAUSE)
			{
				pauseImmediately();
			}
			else if (nextCmd == STOP)
			{
				if (_stopCB)
				{
					_stopCB(static_cast<Sound*>(this), _stopCBData);
				}
				stopImmediately();
			}
		}
	}

	ALint Sound::getBuffer()
	{
		return _buffer;
	}

	bool Sound::isLooping() const
	{
		unsigned int looping = getState(LOOP);   
		if (looping)
		{
			return true;
		}
		else
		{
			return false;
		}
	}

	bool Sound::isListenerRelative() const
	{
		return _listenerRelative;
	}

	void Sound::setBuffer(ALint b)
	{
		if (b == 0)
		{
			return;
		}
		if (alIsBuffer(b) == AL_FALSE)
		{
			return;
		}
		if (IsSource(_source))
		{
			alSourcei(_source, AL_BUFFER, b);
		} 
		_buffer = b;
	}

	void Sound::setPlayCallback(CallBack cb, void* param)
	{
		_playCB = cb;
		_playCBData = _playCB ? param : NULL;
	}

	void Sound::setStopCallback(CallBack cb, void* param)
	{
		_stopCB  = cb;
		_stopCBData = _stopCB ? param : NULL;
	}

	void Sound::setSource(ALuint s)
	{
		_source = s;
		if (_source != AL_NONE)
		{
			_userDefinedSource = true; 
		}
	}

	void Sound::play()
	{
		setState(PLAY);
		_command.push(PLAY);
	}

	void Sound::setSoundMode(unsigned int mode)
	{
		_mode = mode;
		if(_mode == 0)
		{
			removeChild(_circleShape);
			play();
		}
		else
		{
			addChild(_circleShape);
			pause();
		}
	}

	bool Sound::playImmediately()
	{
		if(_mode == 0)
		{
			ALint buf = getBuffer();
			if (alIsBuffer(buf) == AL_FALSE)
			{
				return false;
			}
			setState(PLAY);
			if (! restoreSource())
			{
				return false; // unable to restore source
			}
			alSourcePlay(_source);
		}
		return true;
	}

	void Sound::pause()
	{   
		_command.push(PAUSE);
	}

	void Sound::pauseImmediately()
	{
		unsigned int paused = getState(PAUSE);
		if (! paused)
		{
			setState(PAUSE);
			if (IsSource(_source))
			{
				alSourcePause(_source);
			}
		}
		else
		{
			resetState(PAUSE);
			if (getState(PLAY))
			{
				playImmediately();
			}
		}
	}

	void Sound::stop()
	{
		setState(STOP);
		_command.push(STOP);
	}

	void Sound::stopImmediately()
	{
		setState(STOP);
		if (IsSource(_source) && !_userDefinedSource)
		{
			releaseSource();
		}
	}

	void Sound::rewind()
	{
		setState(REWIND);
		_command.push(REWIND);
	}

	void Sound::rewindImmediately()
	{
		setState(REWIND);
		if (IsSource(_source))
		{
			alSourceRewind(_source);
		}
	}

	void Sound::setLooping(bool loop)
	{
		int loopInt;
		if (loop)
		{
			setState(LOOP);
			loopInt = 1;
		}
		else
		{
			setState(UNLOOP);
			loopInt = 0;
		}
		if (IsSource(_source))
		{
			alSourcei(_source, AL_LOOPING, loopInt);
		}   
	}

	void Sound::setGain(float gain)
	{
		float cgain = gain<0.0f?0.0f:gain;
		cgain = gain>1.0f?1.0f:gain;
		if (IsSource(_source))
		{
			alSourcef(_source, AL_GAIN, cgain);
			alGetSourcef(_source, AL_GAIN, &gain);
		}
		_gain = cgain;
	}

	float Sound::getGain() const
	{
		return _gain;
	}

	void Sound::setPlayTimeOffset(float seconds)
	{
		float cseconds = seconds<0.0f?0.0f:seconds;
		cseconds = seconds>1.0f?1.0f:seconds;
		int isSource = IsSource(_source);
		if (isSource == AL_TRUE)
		{      
			alSourcef(_source, AL_SEC_OFFSET, cseconds);
		}
		_secondOffset = seconds;
	}

	float Sound::getPlayTimeOffset() const
	{
		return _secondOffset;
	}

	void Sound::setPitch(float pitch)
	{
		float cpitch = pitch<0.000001f?0.000001f:pitch;
		cpitch = pitch>128.0f?128.0f:pitch;
		_pitch = cpitch;
		if (IsSource(_source) == AL_FALSE)
		{
			return;
		}
		alSourcef(_source, AL_PITCH, pitch);
		ALenum error = alGetError();
		if (error != AL_NO_ERROR && pitch > 2.0)
		{
			pitch = 2.0f;
			alSourcef(_source, AL_PITCH, pitch);
			alGetSourcef(_source, AL_PITCH, &pitch);
		}
		_pitch = pitch;
	}

	float Sound::getPitch() const
	{
		return _pitch;
	}

	void Sound::setListenerRelative(bool relative)
	{
		if (IsSource(_source))
		{
			alSourcei(_source, AL_SOURCE_RELATIVE, relative ? 1 : 0);
		}
		_listenerRelative = relative;
	}

	void Sound::setTransform(const CoreBase::Transform& xform, CoreBase::Transformable::CoordSysEnum cs)
	{
		CoreBase::Transformable::setTransform(xform, cs);
		CoreBase::Transform transform;
		osg::Vec3         pos(0.0f, 0.0f, 0.0f);
		osg::Vec3         dir(0.0f, 0.0f, 0.0f);
		getTransform(transform, ABS_CS);
		transform.getTranslation(pos);
		transform.getRotation(dir);
		setPosition(pos);
		setDirection(dir);
	}

	void Sound::setPosition(const osg::Vec3& pos)
	{
		if (IsSource(_source) == AL_TRUE)
		{
			alSource3f(_source, AL_POSITION, pos[0], pos[1], pos[2]);
		}
		_position = pos;
	}

	void Sound::getPosition(osg::Vec3& pos) const
	{
		pos = _position;
	}

	osg::Vec3& Sound::getPosition()
	{
		return _position;
	}

	void Sound::setDirection(const osg::Vec3& dir)
	{
		if (IsSource(_source) == AL_TRUE)
		{   
			alSource3f(_source, AL_DIRECTION, dir[0], dir[1], dir[2]);
		}
		_direction = dir;
	}

	void Sound::getDirection(osg::Vec3& dir) const
	{
		dir = _direction;   
	}

	osg::Vec3 Sound::getDirection()
	{
		return _direction;
	}

	void Sound::setVelocity(const osg::Vec3& vel)
	{
		if (IsSource(_source) == AL_TRUE)
		{
			alSource3f(_source, AL_VELOCITY, vel[0], vel[1], vel[2]);
		}
		_velocity = vel;
	}

	void Sound::getVelocity(osg::Vec3& vel) const
	{
		vel = _velocity;
	}

	osg::Vec3 Sound::getVelocity()
	{
		return _velocity;
	}

	void Sound::setMaxDistance(float dist)
	{
		float max = dist>0.0f?dist:0.0f;
		if (IsSource(_source) == AL_TRUE)
		{
			alSourcef(_source, AL_MAX_DISTANCE, max);
		}
		_maxDistance = max;
		_circleShape->setRadius(_maxDistance);
	}

	float Sound::getMaxDistance() const
	{
		return _maxDistance;
	}

	void Sound::setReferenceDistance(float dist)
	{
		float max = dist>0.0f?dist:0.0f;
		if (IsSource(_source) == AL_TRUE)
		{
			alSourcef(_source, AL_REFERENCE_DISTANCE, max);
		}
		_referenceDistance = max;
	}

	float Sound::getReferenceDistance() const
	{
		return _referenceDistance;
	}

	void Sound::setRolloffFactor(float rolloff)
	{
		float theRolloff = rolloff>0.0f?rolloff:0.0f;
		if (IsSource(_source) == AL_TRUE)
		{
			alSourcef(_source, AL_ROLLOFF_FACTOR, theRolloff);
		}
		_rolloffFactor = theRolloff;
	}

	float Sound::getRolloffFactor() const
	{
		return _rolloffFactor;
	}

	void Sound::setMinGain(float gain)
	{
		float cgain = gain<0.0f?0.0f:gain;
		cgain = gain>1.0f?1.0f:gain;
		if (IsSource(_source))
		{
			alSourcef(_source, AL_MIN_GAIN, cgain);
		}
		_minGain = cgain;
	}

	float Sound::getMinGain() const
	{
		return _minGain;
	}

	void Sound::setMaxGain(float gain)
	{
		float cgain = gain<0.0f?0.0f:gain;
		cgain = gain>1.0f?1.0f:gain;
		if (IsSource(_source))
		{
			alSourcef(_source, AL_MAX_GAIN, cgain);
		}
		_maxGain = cgain;
	}

	float Sound::getMaxGain() const
	{
		return _maxGain;
	}

	//Sound::FrameData* Sound::CreateFrameData() const
	//{
	//   FrameData* fd = new FrameData(GetGain(),GetPitch(),IsPlaying());
	//   return fd;
	//}
	//
	//void Sound::UseFrameData(const FrameData* fd)
	//{
	//   setGain(fd->_gain);
	//   setPitch(fd->_pitch);
	//   if (!IsPlaying())
	//   {
	//      if (fd->mPlaying)
	//      {
	//         Play();
	//      }
	//   }
	//   else  // sound is playing
	//   {
	//      if (!fd->mPlaying)
	//      {
	//         Stop();
	//      }
	//   }
	//}
	//
	//DOMElement* Sound::Serialize(const FrameData* d, XERCES_CPP_NAMESPACE_QUALIFIER DOMDocument* doc) const
	//{
	//   XMLCh* SOUND = XMLString::transcode("Sound");
	//   DOMElement* element = doc->createElement(SOUND);
	//   XMLString::release(&SOUND);
	//   XMLCh* MYNAME = XMLString::transcode(GetName().c_str());
	//   XMLCh* NAME = XMLString::transcode("Name");
	//   element->setAttribute(NAME, MYNAME);
	//   XMLString::release(&NAME);
	//   XMLString::release(&MYNAME);
	//   DOMElement* gelement = dtUtil::Serializer::ToFloat(d->_gain, "Gain", doc);
	//   element->appendChild(gelement);
	//   DOMElement* pelement = dtUtil::Serializer::ToFloat(d->_pitch, "Pitch", doc);
	//   element->appendChild(pelement);
	//   DOMElement* playelement = dtUtil::Serializer::ToInt(d->mPlaying, "Playing", doc);
	//   element->appendChild(playelement);
	//   return element;
	//}
	//
	//float Sound::GetDurationOfPlay() const
	//{
	//   int dataSize = 0, bitsPerSample = 0, numChannels = 0;
	//   int samplesPerSecond = 0;
	//   if (_buffer != AL_NONE && alIsBuffer(_buffer)) 
	//   {
	//      alGetBufferi(_buffer, AL_SIZE,      &dataSize);         // Size in bytes of the audio buffer data.
	//      CheckForError("Attempt to get size of data buffer.", __FUNCTION__, __LINE__);
	//      alGetBufferi(_buffer, AL_BITS,      &bitsPerSample);    // The number of bits per sample for the data contained in the buffer.
	//      CheckForError("Attempt to get bits per sample in buffer.", __FUNCTION__, __LINE__);
	//      alGetBufferi(_buffer, AL_CHANNELS,  &numChannels);      // The number of channels for the data contained in the buffer.
	//      CheckForError("Attempt to get # of channels in buffer.", __FUNCTION__, __LINE__);
	//      alGetBufferi(_buffer, AL_FREQUENCY, &samplesPerSecond); // The number of samples per second for the data contained in the buffer.
	//      CheckForError("Attempt to get frequency of buffer.", __FUNCTION__, __LINE__);
	//   }
	//
	//   const float nAvgBytesPerSec = float(samplesPerSecond * numChannels * bitsPerSample) / 8;
	//   const float flDurationSeconds = (dataSize / nAvgBytesPerSec ) / GetPitch();
	//   return flDurationSeconds;
	//}
}